package com.helloscala.model

import scala.xml.{NodeSeq, Unparsed}
import scala.collection.mutable

import org.joda.time.DateTime
import org.bson.types.ObjectId
import org.squeryl.{Session, KeyedEntity}
import com.novus.salat.annotations.raw.Ignore

import net.liftweb.common.Box

import yangbajing.util.Pagination
import yangbajing.persistence.SquerylEntrypoint._
import yangbajing.util.Imports.Y

import com.helloscala.helper.MarkdownHelpers

object MTopic {
  def haveSlug(slug: String): Boolean =
    size(slug = Y.option(slug)) > 0L

  def insert(topic: MTopic, _tags: List[String]): Box[MTopic] = Y.tryBox(
    transaction {
      val tags = _tags.distinct.map(_.toLowerCase)
      val ret = Entities.topics insert topic
      Entities.tags insert tags.map(tag => MTag(topic.id, tag))
      ret
    }
  )

  def save(topic: MTopic, _removeTags: List[String], _newTags: List[String]): Box[MTopic] = Y.tryBox(
    transaction {
      val removeTags = _removeTags.distinct.map(_.toLowerCase)
      val newTags = _newTags.distinct.map(_.toLowerCase)
      //      Entities.topics update topic

      update(Entities.topics)(a =>
        where(a.id === topic.id)
          set(
          a.author := topic.author,
          a.content := topic.content,
          a.slug := topic.slug,
          a.title := topic.title,
          a.description := topic.description
          ))

      for (tag <- removeTags) Entities.tags.deleteWhere(v => v.ref_id === topic.id and v.tag === tag)

      if (newTags.nonEmpty)
        Entities.tags insert newTags.map(tag => MTag(topic.id, tag))

      topic
    })

  def incrementById(id: String, step: Long) =
    inTransaction(update(Entities.topics)(a =>
      where(a.id === id)
        set (a.comment_count := a.comment_count plus step)
    ))

  def findAllPopular(limit: Int): List[MTopic] = transaction {
    from(Entities.topics)(a => select(a) orderBy (a.comment_count.desc)).page(0, limit).toList
  }

  def findOneById(id: String): Box[MTopic] =
    transaction(from(Entities.topics)(a =>
      where(
        if (ObjectId.isValid(id)) a.id === id
        else a.slug === id
      ) select (a)).singleOption)

  def size(
            id: Option[String] = None,
            slug: Option[String] = None,
            author: Option[String] = None,
            title: Option[String] = None,
            tagId: Option[String] = None): Long =
    transaction(from(Entities.topics, Entities.tags)((a, t) =>
      where(
        a.id === t.ref_id and
          (a.id === id.?) and
          (a.slug === slug.?) and
          (a.author === author.?) and
          (a.title === title.?) and
          (t.tag === tagId.?)
      ) compute (countDistinct(a.id))))

  def findAllForTag(offset: Int, limit: Int,
                    title: Option[String] = None,
                    author: Option[String] = None,
                    tagId: Option[String] = None): List[MTopic] =
    transaction(
      from(Entities.topics, Entities.tags)((a, t) =>
        where(
          a.id === t.ref_id and
            (a.title === title.?) and
            (a.author === author.?) and
            (t.tag === tagId.?)
        ) select (a) orderBy (a.created_at.desc)).page(offset, limit).toList)

  def findAll(offset: Int, limit: Int,
              title: Option[String] = None,
              author: Option[String] = None): List[MTopic] =
    transaction(from(Entities.topics)(a =>
      where(
        (a.title === title.?) and
          (a.author === author.?)
      ) select (a) orderBy (a.created_at.desc)).page(offset, limit).toList)

  def pagination(_curPage: Int, _limit: Int,
                 author: Option[String] = None,
                 title: Option[String] = None,
                 tagId: Option[String] = None) = new Pagination[MTopic] {
    val curPage = _curPage
    val limit = _limit

    val total: Long = size(author = author, title = title, tagId = tagId)
    val page: List[MTopic] =
      if (tagId.isDefined) findAllForTag(offset, limit, author = author, title = title, tagId = tagId)
      else findAll(offset, limit, author = author, title = title)
  }


  def findTagAll(offset: Int, limit: Int,
                 topicId: Option[String] = None,
                 author: Option[String] = None): List[Tag] =
    try
      transaction {
        val sqlBuffer =
          new StringBuilder( """select at.tag, count(at.tag) tag_count from tag_ at, topic_ a where a.id = at.ref_id""")

        topicId match {
          case Some(v) if ObjectId.isValid(v) => sqlBuffer append " and a.id = ?"
          case Some(v) => sqlBuffer append " and a.slug = ?"
          case _ => // do nothing
        }

        for (v <- author) sqlBuffer append " and a.author = ?"

        val sql = sqlBuffer.toString + " group by at.tag order by tag_count desc offset ? limit ?"

        val conn = Session.currentSession.connection
        val pstmt = conn.prepareStatement(sql)

        var idx = 0
        for (v <- topicId) {
          idx += 1
          pstmt.setString(idx, v)
        }
        for (v <- author) {
          idx += 1
          pstmt.setString(idx, v)
        }
        pstmt.setInt(idx + 1, offset)
        pstmt.setInt(idx + 2, limit)

        val rs = pstmt.executeQuery()
        val buffer = mutable.ArrayBuffer[Tag]()
        while (rs.next()) {
          buffer += Tag(rs.getString(1), rs.getLong(2))
        }
        buffer.toList
      }
    catch {
      case e: Exception =>
        Nil
    }

}

case class MTopic(
                   author: String,
                   title: String,
                   content: String,
                   slug: Option[String] = None, // 用户友好的名称
                   comment_count: Long = 0,
                   description: Option[String] = None,
                   created_at: DateTime = DateTime.now,
                   id: String = ObjectId.get.toString) extends KeyedEntity[String] {
  def this() = this("", "", "")

  @Ignore
  lazy val tags: List[Tag] = MTopic.findTagAll(0, 128, topicId = Option(id))

  @Ignore
  lazy val tagIds: List[String] = tags.map(_.tag)

  @Ignore
  lazy val contentNodeSeq: NodeSeq = MarkdownHelpers.parseToNodeSeq(content)
}





